﻿using System;
using System.Configuration;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
using Elderos.Articles;
using Elderos.Highlight;
using Elderos.Highlight.Search;
using Elderos.Ontology;
using Elderos.Utils.Logging;
using Elderos.Utils.SQL;
using Elderos.Utils.Serialization;
using Elderos.Utils.Services;

namespace Elderos.HighlightService
{
    public partial class HighlighterService : TaskQueueService<QueuedArticle, Article>
    {
        private readonly Highlighter _highlighter;

        private readonly IArticlesData _articlesData;

        private string _ontologyString = ConfigurationManager.ConnectionStrings["Ontology"].ConnectionString;
        private string _articlesString = ConfigurationManager.ConnectionStrings["Articles"].ConnectionString;

        public HighlighterService()
            : base(null, int.Parse(ConfigurationManager.AppSettings["maxTasksAtOnce"]))
        {
            string ontologyString = _ontologyString;
            string articlesString = _articlesString;

            _articlesData = new ArticlesData(articlesString);

            var ontology = new OntologyData(ontologyString);
            ontology.Initialize();
            _highlighter = new Highlighter(
                new[]{new RabinKarpSearcher(ontology)},
                new SearchResultAdapter(), 
                new RuleRanker(ontology),
                new SimpleAmbiguityResolver(1),
                ontology
                );
            Logger.Info("Initializing HighlightService...");
            Logger.Info("HighlightService initialized.");
        }
        #region TaskQueueService members
        /// <summary>
        /// Dequeues an queueElement to be processed as a queueElement
        /// </summary>
        
        /// <returns>A queue queueElement, which can be processed</returns>
        protected override QueuedArticle Dequeue()
        {
            QueuedArticle element = _articlesData.DequeueArticleAsync().Result;

            return element;
        }

        /// <summary>
        /// Processes the <paramref name="queueElement">queueElement</paramref>.
        /// </summary>
        /// <param name="queueElement">Task to be processed</param>
        /// <param name="token">Cancellation token for cancellation support</param>
        /// <returns>Result of processing</returns>
        protected override Article ProcessTask(QueuedArticle queueElement, CancellationTokenSource token)
        {
            token.Token.ThrowIfCancellationRequested();
            Logger.Info("Processing ArticleID={0}...", queueElement.ArticleID);
            token.Token.ThrowIfCancellationRequested();

            Article article = _articlesData.GetArticleAsync(queueElement.ArticleID).Result;

            var result = _highlighter.Highlight(article.ToHighlightableString());

            article.Highlight = XDocument.Parse(XSerializer.SerializeObject(result));

            var objcount = result.Positions.SelectMany(x => x.EntitiesFlattened).Count();
            Logger.Info("ArticleID={0} processed, total objects count={1}", queueElement.ArticleID, objcount);
            return article;
        }

        /// <summary>
        /// Saves the processed queueElement
        /// </summary>
        /// <param name="queueElement">Processed queue queueElement</param>
        /// <param name="result">Result returned by the ProcessTask method</param>
        protected override void Save(QueuedArticle queueElement, Article result)
        {
            Logger.Info("Saving ArticleID={0}...", queueElement.ArticleID);

            using (var connection = new SqlConnection(_articlesString))
            using (var transaction = connection.BeginTransaction())
            {
                try
                {
                    var task1 = _articlesData.UpdateArticleAsync(result, transaction);
                    
                    queueElement.Status = 3;
                    var task2 = _articlesData.UpdateQueuedArticleAsync(queueElement, transaction);
                    Task.WaitAll(task1, task2);
                    transaction.Commit();
                }
                catch (Exception)
                {
                    transaction.Rollback();
                    throw;
                }
            }
            
            Logger.Info("ArticleID={0} saved.", queueElement.QueueID);
        }

        /// <summary>
        /// Marks the queueElement as failed in the queue
        /// </summary>
        /// <param name="queueElement">Element to be marked as failed</param>
        protected override void Fail(QueuedArticle queueElement)
        {
            Logger.Info("Failing ArticleID={0}...", queueElement.ArticleID);

            queueElement.Status = -1;
            _articlesData.UpdateQueuedArticleAsync(queueElement).Wait();
                
            Logger.Info("ArticleID={0} failed.", queueElement.ArticleID);
        }

        /// <summary>
        /// Marks the queueElement as cancelled (usually it must be returned into the initial state)
        /// </summary>
        /// <param name="queueElement">Element to be marked as cancelled</param>
        protected override void Cancel(QueuedArticle queueElement)
        {
            Logger.Info("Cancelling ArticleID={0}...", queueElement.ArticleID);

            queueElement.Status = 0;
            _articlesData.UpdateQueuedArticleAsync(queueElement).Wait();

            Logger.Info("ArticleID={0} cancelled.", queueElement.ArticleID);
        }
        #endregion
    }
}
